<!DOCTYPE html>
<html lang="zh-cn">
<head>
  
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
  <title>
从源码看Objective-C的对象模型（一） |
穷折腾</title>
  <link rel="stylesheet" href="/static/css/style.css" />
  <link rel="stylesheet" href="/static/css/pygments.css" />
  <link rel="alternate" type="application/rss+xml" title="RSS" href="http://blog.zorro.im/rss.xml" />
  <link rel="icon" type="image/png" href="/static/img/favicon.png">
</head>
<body>
    <div class="page-wrap">
        <section class="main-header regular">
            <div class="vertical-container">
                <div class="wrapper clearfix">
                    <header>
                        <a href="/">
                            <h1 id="main-title">穷折腾</h1>
                            <h2 id="main-subtitle">zqqf16 的个人博客</h2>
                        </a>
                    </header>
                    <nav id="main-nav" role="navigation">
                        <a href="/" class="selected">主页</a>
                        <a href="/posts/about.html" class="">关于</a>
                        <a href="https://github.com/zqqf16">Github</a>
                    </nav>
                </div>
            </div>
        </section>
        <section class="main-content">
            
<article class="main-content">
    <div class="wrapper">
        <header class="section-header">
            <h1 class="section-title">从源码看Objective-C的对象模型（一）</h1>
            <span class="section-subtitle"><time datetime="2014-03-05" pubdate="">2014-03-05</time></span>
        </header>
        <section class="post-content">
        	<h2 id="_1">前言</h2>
<p>刚接触Objective-C的时候，曾被一个bug折磨得很痛苦，后来才发现是我对Category理解错了，不知道它对所有该类的实例都会起作用。当时就决定要好好研究一下Objective-C的对象模型。正好最近手头的活忙完了，可以有时间做个总结。</p>
<p>本文所参照的代码是clang rewrite之后的C++代码，以及Apple开源的Runtime代码，地址<a href="http://www.opensource.apple.com/source/objc4/">在这</a>。Apple官方貌似没有提供一个打包下载的地址，我在Github上fork了一份别人整理过的437.1版本的，可以到<a href="https://github.com/zqqf16/OBJC4-437.1-Runtime">这里</a>查看。</p>
<h2 id="_2">基本概念</h2>
<p>对于Objective-C有关对象、类、元类的基本概念，我就不细说了，和Python很像。这里有三篇文章，对我当时研究的启发很大，大家可以参考一下。</p>
<ul>
<li><a href="http://blog.csdn.net/wzzvictory/article/details/8592492">Objective-C对象之类对象和元类对象（一）</a></li>
<li><a href="http://blog.devtang.com/blog/2013/10/15/objective-c-object-model/">Objective-C对象模型及应用</a></li>
<li><a href="http://www.cnblogs.com/kesalin/archive/2012/01/19/objc_class_object.html">深入浅出Cocoa之类与对象</a></li>
</ul>
<p>这里只做个总结：</p>
<ol>
<li>所有对象都是一个结构体，它的第一个成员是一个指向<code>objc_class</code>类型的指针——<code>isa</code>。或者说所有第一个成员是<code>objc_class</code>指针类型的结构体都是对象。</li>
<li>实例对象的<code>isa</code>指向它的类对象，类对象的<code>isa</code>指向元类，元类的<code>isa</code>指向根元类，根元类的指向它自己。</li>
</ol>
<h2 id="_3">实例、类、元类初探</h2>
<p>新建一个NSObject的子类，如下：</p>
<div class="codehilite"><pre><span class="cm">/* FirstClass.h */</span>
<span class="cp">#import &lt;Foundation/Foundation.h&gt;</span>

<span class="k">@interface</span> <span class="nc">FirstClass</span> : <span class="bp">NSObject</span>
<span class="p">{</span>
    <span class="bp">NSString</span> <span class="o">*</span><span class="n">className</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">@end</span>

<span class="cm">/* FirstClass.m */</span>
<span class="cp">#import &quot;FirstClass.h&quot;</span>

<span class="k">@implementation</span> <span class="nc">FirstClass</span>

<span class="k">@end</span>
</pre></div>


<p>然后执行<code>clang -rewrite-objc FirstClass.m</code>，这样就会在当前目录生成一个名叫<code>FirstClass.cpp</code>的文件，打开文件（注意，因为包含了<code>Foundation.h</code>，所以生成的cpp文件巨大，可以忽略前1W行）可以看到，FirstClass被改写成了这样：</p>
<div class="codehilite"><pre><span class="k">typedef</span> <span class="k">struct</span> <span class="n">objc_object</span> <span class="n">FirstClass</span><span class="p">;</span>
</pre></div>


<p>成了<code>objc_object</code>类型的结构体。<code>objc_object</code>的声明如下：</p>
<div class="codehilite"><pre><span class="k">struct</span> <span class="n">objc_object</span> <span class="p">{</span>
    <span class="n">Class</span> <span class="n">isa</span> <span class="p">;</span>
<span class="p">};</span>
</pre></div>


<p>也就是说，当我们用<code>FirstClass *fc;</code>这样的形式去声明一个变量的时候，其实这个变量就是一个指向<code>objc_object</code>类型的指针，也就是上面我总结的，他指向的是一个对象。</p>
<p>接着往下看，会发现以下这样的代码：</p>
<div class="codehilite"><pre><span class="k">extern</span> <span class="s">&quot;C&quot;</span> <span class="kr">__declspec</span><span class="p">(</span><span class="n">dllimport</span><span class="p">)</span> <span class="k">struct</span> <span class="kt">_class_t</span> <span class="n">OBJC_METACLASS_</span><span class="err">$</span><span class="n">_NSObject</span><span class="p">;</span>

<span class="k">extern</span> <span class="s">&quot;C&quot;</span> <span class="kr">__declspec</span><span class="p">(</span><span class="n">dllexport</span><span class="p">)</span> <span class="k">struct</span> <span class="kt">_class_t</span> <span class="n">OBJC_METACLASS_</span><span class="err">$</span><span class="n">_FirstClass</span> <span class="n">__attribute__</span> <span class="p">((</span><span class="n">used</span><span class="p">,</span> <span class="n">section</span> <span class="p">(</span><span class="s">&quot;__DATA,__objc_data&quot;</span><span class="p">)))</span> <span class="o">=</span> <span class="p">{</span>
    <span class="mi">0</span><span class="p">,</span> <span class="c1">// &amp;OBJC_METACLASS_$_NSObject,</span>
    <span class="mi">0</span><span class="p">,</span> <span class="c1">// &amp;OBJC_METACLASS_$_NSObject,</span>
    <span class="mi">0</span><span class="p">,</span> <span class="c1">// (void *)&amp;_objc_empty_cache,</span>
    <span class="mi">0</span><span class="p">,</span> <span class="c1">// unused, was (void *)&amp;_objc_empty_vtable,</span>
    <span class="o">&amp;</span><span class="n">_OBJC_METACLASS_RO_</span><span class="err">$</span><span class="n">_FirstClass</span><span class="p">,</span>
<span class="p">};</span>

<span class="k">extern</span> <span class="s">&quot;C&quot;</span> <span class="kr">__declspec</span><span class="p">(</span><span class="n">dllimport</span><span class="p">)</span> <span class="k">struct</span> <span class="kt">_class_t</span> <span class="n">OBJC_CLASS_</span><span class="err">$</span><span class="n">_NSObject</span><span class="p">;</span>

<span class="k">extern</span> <span class="s">&quot;C&quot;</span> <span class="kr">__declspec</span><span class="p">(</span><span class="n">dllexport</span><span class="p">)</span> <span class="k">struct</span> <span class="kt">_class_t</span> <span class="n">OBJC_CLASS_</span><span class="err">$</span><span class="n">_FirstClass</span> <span class="n">__attribute__</span> <span class="p">((</span><span class="n">used</span><span class="p">,</span> <span class="n">section</span> <span class="p">(</span><span class="s">&quot;__DATA,__objc_data&quot;</span><span class="p">)))</span> <span class="o">=</span> <span class="p">{</span>
    <span class="mi">0</span><span class="p">,</span> <span class="c1">// &amp;OBJC_METACLASS_$_FirstClass,</span>
    <span class="mi">0</span><span class="p">,</span> <span class="c1">// &amp;OBJC_CLASS_$_NSObject,</span>
    <span class="mi">0</span><span class="p">,</span> <span class="c1">// (void *)&amp;_objc_empty_cache,</span>
    <span class="mi">0</span><span class="p">,</span> <span class="c1">// unused, was (void *)&amp;_objc_empty_vtable,</span>
    <span class="o">&amp;</span><span class="n">_OBJC_CLASS_RO_</span><span class="err">$</span><span class="n">_FirstClass</span><span class="p">,</span>
<span class="p">};</span>
</pre></div>


<p>根据字面意思就可以看明白了，分别定义了两个变量，<code>OBJC_METACLASS_$_FirstClass</code>和<code>OBJC_CLASS_$_FirstClass</code>，前者是元类，后者是类。他们都是<code>_class_t</code>类型的，都是对象。因为是自动改写的，所以变量名都有些奇怪，但并不影响阅读。</p>
<p>这里简单介绍一下<code>__declspec(dllimport)</code>、<code>__declspec(dllexport)</code> 以及 <code>__attribute__ ((used, section ("__DATA,__objc_data")))</code>。前两个分别声明导入和导出函数，一般用于动态链接库，后面的是一个GNU C的扩展，<code>used</code>告诉编译器即使后面没有引用也要编译这段代码，<code>section ("__DATA,__objc_data")</code><s>是说把这段代码编译到<code>__DATA,__objc_data</code>段，而不是默认的代码段。</s>至于为啥要这样，我也不太明白，有待进一步研究。</p>
<blockquote>
<p><strong>2015-1-21 更新</strong></p>
<ol>
<li>"__DATA,__objc_data" 是要把这段数据放在 "__DATA" 段（Segment）的 "__objc_data" 节（Section）。这跟 Mach-O 文件格式的规定有关，类似的还有 "__DATA,__objc_classlist"、"__TEXT,__objc_methodname" 这样的。</li>
<li>这段代码定义了全局变量，应该放在数据段，而不是代码段</li>
</ol>
</blockquote>
<p><code>_class_t</code>的声明如下：</p>
<div class="codehilite"><pre><span class="k">struct</span> <span class="kt">_class_t</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="kt">_class_t</span> <span class="o">*</span><span class="n">isa</span><span class="p">;</span>
    <span class="k">struct</span> <span class="kt">_class_t</span> <span class="o">*</span><span class="n">superclass</span><span class="p">;</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">cache</span><span class="p">;</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">vtable</span><span class="p">;</span>
    <span class="k">struct</span> <span class="kt">_class_ro_t</span> <span class="o">*</span><span class="n">ro</span><span class="p">;</span>
<span class="p">};</span>
</pre></div>


<p>基本可以当成<code>runtime.h</code>中的<code>objc_class</code>类型。</p>
<p>后面还有一段代码，定义了一个初始化函数：</p>
<div class="codehilite"><pre><span class="k">static</span> <span class="kt">void</span> <span class="n">OBJC_CLASS_SETUP_</span><span class="err">$</span><span class="n">_FirstClass</span><span class="p">(</span><span class="kt">void</span> <span class="p">)</span> <span class="p">{</span>
    <span class="n">OBJC_METACLASS_</span><span class="err">$</span><span class="n">_FirstClass</span><span class="p">.</span><span class="n">isa</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">OBJC_METACLASS_</span><span class="err">$</span><span class="n">_NSObject</span><span class="p">;</span>
    <span class="n">OBJC_METACLASS_</span><span class="err">$</span><span class="n">_FirstClass</span><span class="p">.</span><span class="n">superclass</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">OBJC_METACLASS_</span><span class="err">$</span><span class="n">_NSObject</span><span class="p">;</span>
    <span class="n">OBJC_METACLASS_</span><span class="err">$</span><span class="n">_FirstClass</span><span class="p">.</span><span class="n">cache</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">_objc_empty_cache</span><span class="p">;</span>
    <span class="n">OBJC_CLASS_</span><span class="err">$</span><span class="n">_FirstClass</span><span class="p">.</span><span class="n">isa</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">OBJC_METACLASS_</span><span class="err">$</span><span class="n">_FirstClass</span><span class="p">;</span>
    <span class="n">OBJC_CLASS_</span><span class="err">$</span><span class="n">_FirstClass</span><span class="p">.</span><span class="n">superclass</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">OBJC_CLASS_</span><span class="err">$</span><span class="n">_NSObject</span><span class="p">;</span>
    <span class="n">OBJC_CLASS_</span><span class="err">$</span><span class="n">_FirstClass</span><span class="p">.</span><span class="n">cache</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">_objc_empty_cache</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>


<p>可以看到元类的<code>isa</code>、<code>superclass</code>都指向了NSOBject，而类的<code>isa</code>指向了元类、<code>superclass</code>指向了NSObject。</p>
<p>看到这就明白了，类对象以及元类对象是编译时就会创建好的。那么实例对象是怎么创建的，实例变量是怎么保存的呢？且看下一篇分解~</p>
<p>--未完待续--</p>
        </section>
        <div class="post-nav">
    		<!-- Duoshuo Comment BEGIN -->
<div class="ds-thread"></div>
<script type="text/javascript">
	var duoshuoQuery = {short_name:"zqqf16"};
	(function() {
		var ds = document.createElement('script');
		ds.type = 'text/javascript';ds.async = true;
		ds.src = 'http://static.duoshuo.com/embed.js';
		ds.charset = 'UTF-8';
		(document.getElementsByTagName('head')[0]
		|| document.getElementsByTagName('body')[0]).appendChild(ds);
	})();
</script>
<!-- Duoshuo Comment END -->

    	</div>
    </div>

</article>

        </section>
    </div>
    <footer class="main-footer">
        <div class="vertical-container">
            <div class="wrapper">
                <p class="copy">&copy; zqqf16</p>
            </div>
        </div>
    </footer>
    <script>
      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
      ga('create', 'UA-41282906-2', 'auto');
      ga('require', 'displayfeatures');
      ga('send', 'pageview');
    </script>
</body>
</html>
